/**
 * Copyright 2011 Honeywell Federal Manufacturing & Technologies, LLC. This material was produced under U.S. Government
 * contract DE-ACO4-01AL66850, Honeywell Federal Manufacturing & Technologies, LLC, (FM&T) with the U. S. Department of
 * Energy for the operation of the Kansas City Plant . The U.S. Government has rights to use, reproduce, and distribute
 * this software.  NEITHER THE GOVERNMENT NOR Honeywell Federal Manufacturing & Technologies, LLC, MAKES ANY WARRANTY,
 * EXPRESS OR IMPLIED, OR ASSUMES ANY LIABILITY FOR THE USE OF THIS SOFTWARE.  If software is modified to produce
 * derivative works, such modified software should be clearly marked, so as not to confuse it with the version available
 * from FM&T.
 *
 * Additionally, this program is free software; you can redistribute it and/or modify it under the terms of the GNU
 * General Public License Version 3 published by the Free Software Foundation. Accordingly, this program is distributed in
 * the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with this library.
 * If not, see http://www.gnu.org/licenses/.
 */
package org.braintrain.tapestry.aso;

import com.kcp.ko.fmtnmlib.service.HibernatePersistenceService;
import com.kcp.ko.fmtnmlib.types.BaseDomainObject;
import edu.emory.mathcs.backport.java.util.Collections;
import org.springframework.beans.factory.annotation.Required;

import java.io.Serializable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * Manages the state of the in-edit transient Hibernate (BaseDomainObject) objects.
 *
 * This allows the various components of the system to access a BaseDomainObject via
 * an 'Editable Key' while this ASO handles the re-attaching of the editable objects
 * to the Sessions.  This helps avoid some problems with lazy loading and transient
 * Entities.
 *
 * When the given object is ready to be 'edited' by the system, otherwise known as the
 * Tapestry Rewind, the editable flag can be set to true and the object is then handled
 * in a strictly transient state.  
 *
 * Note, this does not work well across separate Sessions when the BaseDomainObject is fairly
 * complicated (many potential lazy-loaded elements in the object graph).
 *
 * @author John Ericksen
 *
 */

@SuppressWarnings("unchecked")
public class EditingASO implements Serializable{

    private static final long serialVersionUID = 9070280033209741623L;
    
    private transient HibernatePersistenceService genericHibernateService;
    private Map<Long, BaseDomainObject> editable = Collections.synchronizedMap(new HashMap<Long, BaseDomainObject>());
    private Set<Long> stale = Collections.synchronizedSet(new HashSet<Long>());
    private Map<Long, Long> referenceIds = Collections.synchronizedMap(new HashMap<Long, Long>());

    private Long currentKey = 0L;

    /**
     * Starts the editing process... this associates a given editable objects
     * id with a key.  This key is used to reference the object pre-edit and during the
     * edit.
     *  value = produceEditable(clazz, key, newValue);
     * @param <T>
     * @param checkin
     * @return editable key
     */
    public synchronized <T extends BaseDomainObject> Long checkinEditable(T checkin){
        currentKey = currentKey + 1;
        if(checkin.getId() == null){
            editable.put(currentKey, checkin);
        }
        else{
            referenceIds.put(currentKey, checkin.getId());
        }
        return currentKey;
    }

    /**
     * Used to reference the given editable object.  While the editing flag is off the
     * given object is directly referenced from the hibernate session.  While the editing
     * flag is turned on, the object is drawn out of the hibernate session and stored
     * in the 'editable' map.
     *
     * @param <T>
     * @param clazz
     * @param key
     * @param editing
     * @param newValue
     * @return editable object
     */
    public synchronized <T extends BaseDomainObject> T getEditable(Class<T> clazz, Long key, boolean editing, T newValue){
        final T value;

        if(editing){
            //editing case
            if(!editable.containsKey(key) || stale.contains(key)){
                editable.put(key, produceEditable(clazz, key, newValue));
            }
            value = (T)editable.get(key);
        }
        else{

            if(editable.containsKey(key)){
                stale.add(key);
                value = (T)editable.get(key);
            }
            else{
                //standard no-edit, not-transient case
                value = produceEditable(clazz, key, newValue);
            }
        }

        return value;
    }

    /**
     *
     * @param <T>
     * @param clazz
     * @param key
     * @param newValue
     * @return
     */
    private <T extends BaseDomainObject> T produceEditable(Class<T> clazz, Long key, T newValue){
        T value;
        Long id = referenceIds.get(key);
        if(id == null){
            value = newValue;
        }
        else{
            value = genericHibernateService.getById(clazz, id);
        }
        stale.remove(key);
        return value;
    }

    /**
     * Called once the edits have finished and the application is done dealing
     * with the object.  This clears it out of all references to the object.
     *
     * @param <T>
     * @param key
     * @return editable object
     */
    public <T extends BaseDomainObject> T finishEdit(Long key){
        referenceIds.remove(key);
        stale.remove(key);
        return (T)editable.remove(key);

    }

    /**
     * @param genericHibernateService the genericHibernateService to set
     */
    @Required
    public void setGenericHibernateService(HibernatePersistenceService genericHibernateService) {
        this.genericHibernateService = genericHibernateService;
    }
}